/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package zombiedefense.map;

import zombiedefense.framework.PlayerManager;
import zombiedefense.sprites.Player;
import zombiedefense.util.PriorityQueue;

/**
 * This class uses the A* pathfinding algorithm to find the
 * shortest path between two points in a graph (represented by
 * a TileMap).
 * @author Connor Willison
 */
public class PathFinder {
    
    private TileMap map;
    private PriorityQueue<Node> openList;
    private PlayerManager player;
    
    public PathFinder(TileMap map,PlayerManager player){
	this.map = map;
	this.player = player;
        openList = new PriorityQueue<Node>();
    }
    
    public Player getPlayer(){
	return player.getPlayer();
    }
    
    public Path getPath(double startx,double starty,double endx,double endy){
        Node startNode = map.getNearestPassableTile(startx,starty);   //get start node for use later 
        Node endNode = map.getTile(endx,endy);         //get end node for use later
        
	if(startNode == null || endNode == null)return null; //avoid null pointers (these occur if the path goes offscreen)
	
        Node currentNode = startNode;                  //start with the first node
        
        calculate(currentNode,startNode,endNode);      //calculate values for first node
	
//	//add it to the closed list
	currentNode.setListValue(Node.ListValue.ClosedList);
        
        //run algorithm until endNode is found
        while(currentNode != endNode){
	    
            //examine its neighbors, calculating their dist from start and dist to goal values
            //and adding them to the open list as necessary
            examineNeighbors(currentNode,endNode);

            //put current node on the closed list
	    openList.remove(currentNode);
            currentNode.setListValue(Node.ListValue.ClosedList);
            
            //there is no possible path if the list is empty at this point
            if(openList.isEmpty())return null;

            //select the next node from the open list (lowest sum distance)
            currentNode = openList.getFirst();
                    
        }
	
        
        //reset every node's list value --> may be replaced in the future by a quicker method
        map.resetTiles();
        
        //clear the open list
        openList.clear();
        
        //current node should be the end node --> time to trace the path
        Path path = new Path();
        path.add(currentNode);
	
        while(currentNode != startNode && currentNode != null){
	    path.add(currentNode);
            currentNode = currentNode.getParent();
        }
        
        //return the path
        return path;
    }
    
    /**
     * Checks each node around the specified node.
     * Sets each nodes parent to n, calculates their
     * 3 aStar values, and adds them to the open list. 
     * @param x
     * @param y 
     */
    private void examineNeighbors(Node currentNode,Node endNode){
        Node neighbor;
        int realx,realy;
        
        for(int x = -1; x <= 1; x++){
            for(int y = -1; y <= 1; y++){
                
                realx = currentNode.getX() + x;
                realy = currentNode.getY() + y;
                
                neighbor = map.getTile(realx,realy);
                
		if(neighbor == null)continue;	//if we hit a boundary
		
                //ignore impassable tiles and those that were already checked
                if(!(map.isSolid(realx,realy) || isOnClosedList(neighbor))){
                    
                    //check if neighbor is already on the open list
                    if(isOnOpenList(neighbor)){
                        //check if this is a better path to the neighbor
                        if(currentNode.isBetterPath(neighbor)){
                            //change the parent to this node
                            neighbor.setParent(currentNode);
                            
                            //recalculate values
                            calculate(neighbor,currentNode,endNode);
                        }else{
                            continue;  //do nothing if it isn't a better path
                        }
                        
                    }else{
                        //set the parent of the node
                        neighbor.setParent(currentNode);

                        //calculate the node's values
                        calculate(neighbor,currentNode,endNode);

                        //add to the open list
                        openList.add(neighbor);
                        neighbor.setListValue(Node.ListValue.OpenList);
                    }  
                } 
            }
        }
    }
    
    private boolean isOnOpenList(Node n){
        return n.getListValue() == Node.ListValue.OpenList;
    }
    
    private boolean isOnClosedList(Node n){
        return n.getListValue() == Node.ListValue.ClosedList;
    }
    
    /**
     * Calculates each of the following values of the nextNode argument:
     * 
     * - distanceFromStart
     * - distanceFromEnd
     * - sumDistance
     * 
     * @param n
     * @param start
     * @param end
     * @return 
     */
    private void calculate(Node nextNode,Node currentNode,Node endNode){
        //calc distance from start --> current + move cost to next node
        nextNode.setDistFromStart(currentNode.getDistFromStart() + currentNode.getMovementCost(nextNode));
        
        //calc distance from end --> manhattan method
        nextNode.setDistFromEnd(nextNode.getManhattanDistance(endNode));
        
        //calc sum distance
        nextNode.setSumDist(nextNode.getDistFromStart() + nextNode.getDistFromEnd());
    }
    
    
}
